﻿using System.Diagnostics;
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Linq.Mapping;
// for FxCop warning suppression
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using PDO.DataPersistence.ServiceProvider;
using PDO.Utilities;

namespace PDO.DataPersistence.Core
{
    public class PDORegistration
    {
        /// <summary>
        /// Initializes a new instance of the TableNameRegistration class.
        /// </summary>
        public PDORegistration(Type type, string tableName)
        {
            Type = type;
            TableName = tableName;
        }

        public readonly Type Type;
        public readonly string TableName;

    }

    public static class PDOMapper
    {
        public struct SProcOperations
        {
            public const string Create = "Insert";
            public const string Read = "Select";
            public const string Update = "Update";
            public const string Delete = "Delete";

            public const string Sync = "Sync";

            public const string AuditCreate = "AuditInsert";
            public const string AuditRead = "AuditSelect";
            public const string AuditDelete = "AuditDelete";
        }

        public static List<PDORegistration> PdoRegistrations = new List<PDORegistration>();

        /// <summary>
        /// Registers a PersistableDataObject with the mapper class.
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static PDORegistration Register(Type type)
        {
            string tableName = PDOMapper.GetSourceDatabaseTableName(type);

            PDORegistration reg = new PDORegistration(type, tableName);

            PdoRegistrations.Add(reg);

            try
            {
                // Calling GetTable with a type that is not yet in the tables collection 
                // automatically adds it.
                DataSession.LINQDataContext.GetTable(type);

                Debug.WriteLine("Registered PDO: " + type.Name);
            }
            catch (Exception ex)
            {
                int foo = 2;
            }
            return reg;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="tableName"></param>
        /// <returns></returns>
        /// <exception cref="KeyNotFoundException"/>        
        public static Type GetPdoTypeByTableName(string tableName)
        {
            PDORegistration reg = PDOMapper.PdoRegistrations.FirstOrDefault(registration => registration.TableName == tableName);
            if (reg == default(PDORegistration))
            {
                throw new KeyNotFoundException();
            }
            return reg.Type;
        }


        /// <summary>
        /// Return database schema.table name for provided PersistableDataObject derived type
        /// </summary>
        /// <param name="t"></param>
        /// <returns></returns>
        public static string GetSourceDatabaseTableName(Type t)
        {
            if (!t.IsSubclassOf(typeof(PersistableDataObject)))
            {
                return "<no source database table>";	// do not translate
            }

            string ret = string.Empty;
            object[] attrs;

            attrs = t.GetCustomAttributes(typeof(TableAttribute), false);
            if (attrs.Length == 1)
            {
                TableAttribute attr = (TableAttribute)attrs[0];
                ret = attr.Name;
            }
            else if (attrs.Length < 1)
            {
                // recurse on base type 
                return GetSourceDatabaseTableName(t.BaseType);
            }

            return ret;
        }


        /// <summary>
        /// Get property name from provided function expression.
        /// </summary>
        /// <param name="expression">expression pointing to property we want the name of</param>
        /// <returns></returns>
        public static string GetNotificationPropertyName(Expression<Func<object>> expression)
        {
            var lambda = expression as LambdaExpression;
            MemberExpression memberExpression;
            if (lambda.Body is UnaryExpression)
            {
                var unaryExpression = lambda.Body as UnaryExpression;
                memberExpression = unaryExpression.Operand as MemberExpression;
            }
            else
            {
                memberExpression = lambda.Body as MemberExpression;
            }
            var constantExpression = memberExpression.Expression as ConstantExpression;
            var propertyInfo = memberExpression.Member as PropertyInfo;

            return propertyInfo.Name;
        }


        /// <summary>
        /// Get primary key property info from System.Data.Linq ColumnAttribute data.
        /// </summary>
        /// <param name="type">type to examine get primary key info from </param>
        /// <returns>property info, or null</returns>
        public static PropertyInfo GetPrimaryKeyPropertyInfo(Type type)
        {
            PropertyInfo pkeyPi = null;
            PropertyInfo[] piArray = type.GetProperties(BindingFlags.Instance | BindingFlags.Public);
            foreach (PropertyInfo pi in piArray)
            {
                ColumnAttribute vra = ReflectionHelper.GetAttribute<ColumnAttribute>(pi);
                if (null != vra && vra.IsPrimaryKey)
                {
                    pkeyPi = pi;
                    break;
                }
            }
            return pkeyPi;
        }


        /// <summary>
        /// Set primary key of PersistableDataObject to provided value.  
        /// CAUTION: this should only be used where absolutely necessary and with full understanding of side-effects.
        /// </summary>
        /// <param name="pdo">PersistableDataObject</param>
        /// <param name="guid">guid</param>
        public static void SetPKeyID(PersistableDataObject pdo, Guid guid)
        {
            PropertyInfo pi = PDOMapper.GetPrimaryKeyPropertyInfo(pdo.GetType());
            pi.SetValue(pdo, guid, null);
        }


        /// <summary>
        /// Get sql select statement parameters for provided type as a string.
        /// </summary>
        /// <param name="type">PersistableDataObject derived type.</param>
        /// <returns>SQL select phrase.</returns>
        public static string GetSqlSelectParams(Type type)
        {
            Assert.IsTrue(typeof(PersistableDataObject).IsAssignableFrom(type));


            PropertyInfo[] piArray = type.GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public);
            StringBuilder sel = new StringBuilder();
            foreach (PropertyInfo pi in piArray)
            {
                ColumnAttribute vra = ReflectionHelper.GetAttribute<ColumnAttribute>(pi);
                if (null != vra)
                {
                    if (vra.Name != null)
                    {
                        sel.Append(string.Format("[{0}] as [{1}], ", vra.Name, pi.Name));
                    }
                    else
                    {
                        sel.Append(string.Format("[{0}], ", pi.Name));
                    }
                }
            }
            string s = sel.ToString();



            //-----------------------------------
            // CX - TEMP
            // The following implementation is intended to replace the reflection based method 
            // above after some time of testing with no exceptions being thrown from the assert below.
            StringBuilder sel2 = new StringBuilder();
            MetaModel mm = DataSession.LINQDataContext.Mapping.MappingSource.GetModel(type);

            Dictionary<string, MetaDataMember> mdmDic = new Dictionary<string, MetaDataMember>();
            foreach (MetaDataMember mdm in mm.GetMetaType(type).PersistentDataMembers)
            {
                if (mdm.IsAssociation == false)
                {
                    mdmDic.Add(mdm.Name, mdm);
                }
            }

            if (type.BaseType != typeof(PersistableDataObject))
            {
                // Remove base members from dictionary, leaving only members declared directly in provided type.
                foreach (MetaDataMember mdm in mm.GetMetaType(type.BaseType).PersistentDataMembers)
                {
                    if (mdmDic.ContainsKey(mdm.Name))
                    {
                        mdmDic.Remove(mdm.Name);
                    }
                }
            }

            StringBuilder sel3 = new StringBuilder();
            foreach (MetaDataMember mdm in mdmDic.Values)
            {
                if (mdm.MappedName != mdm.Name)
                {
                    sel3.Append(string.Format("[{0}] as [{1}], ", mdm.MappedName, mdm.Name));
                }
                else
                {
                    string name = mdm.MappedName;
                    sel3.Append(string.Format("[{0}], ", name));
                }
             ///   mdm.de.DeferredValueAccessor.GetBoxedValue(
            }

            string s3 = sel3.ToString();
            Assert.AreEqual(s, s3); // confirm both methods return same result.
            // Eventually replace reflection with with Linq method after testing confirms.

            // OPTIMIZE  by storing sql selection paramter string in dictionary maintained by PDOMapper.

            //-----------------------------------

            return s.Substring(0, s.Length - 2);


        }




        /// <summary>
        /// Build a sproc name from a provided PersistableDataObject and a base name.
        /// </summary>
        /// <param name="pdo">PersistableDataObject</param>
        /// <param name="nameBase">base name</param>
        /// <returns>sproc name</returns>
        private static string BuildProcName(Type pdoType, string nameBase)
        {
            // Example:
            // For a PersistableDataObject with a corresponding database table name of "[NeuroCare].[Facility]",
            // produce a sproc name with a base name of "Select", i.e. "[NeuroCare].[spgFacilitySelect]".

            string tableName = GetSourceDatabaseTableName(pdoType);

            int i = tableName.LastIndexOf("[") + 1;
            int j = tableName.Length;

            int colonPos = tableName.LastIndexOf("::"); // derived class table names
            if (colonPos > -1)
            {
                j = colonPos + 1;
            }

            string procName = tableName.Substring(0, i) + "spg" + tableName.Substring(i, j - i - 1) + nameBase + "]";

            return procName;
        }

        /// <summary>
        /// Get name of insert stored procedure.
        /// </summary>
        /// <param name="pdo">PersistableDataObject</param>
        /// <returns>sproc name</returns>
        public static string GetInsertProcName(Type pdoType)
        {
            return BuildProcName(pdoType, "Insert");
        }

        /// <summary>
        /// Get name of update stored procedure.
        /// </summary>
        /// <param name="pdo">PersistableDataObject</param>
        /// <returns>sproc name</returns>
        public static string GetUpdateProcName(Type pdoType)
        {
            return BuildProcName(pdoType, "Update");
        }

        /// <summary>
        /// Get name of select stored procedure.
        /// </summary>
        /// <param name="pdo">PersistableDataObject</param>
        /// <returns>sproc name</returns>
        public static string GetSelectProcName(Type pdoType)
        {
            return BuildProcName(pdoType, "Select");
        }

        /// <summary>
        /// Get name of delete stored procedure.
        /// </summary>
        /// <param name="pdo">PersistableDataObject</param>
        /// <returns>sproc name</returns>
        public static string GetDeleteProcName(Type pdoType)
        {
            return BuildProcName(pdoType, "Delete");
        }

        /// <summary>
        /// Get name of sync stored procedure.
        /// </summary>
        /// <param name="pdo">PersistableDataObject</param>
        /// <returns>sproc name</returns>
        public static string GetSyncProcName(Type pdoType)
        {
            return BuildProcName(pdoType, "Sync");
        }

        /// <summary>
        /// Get name of audit stored procedure.
        /// </summary>
        /// <param name="pdo">PersistableDataObject</param>
        /// <returns>sproc name</returns>
        public static string GetAuditProcName(Type pdoType)
        {
            return BuildProcName(pdoType, "AuditInsert");
        }


        public static string GetProcName(Type pdoType, string operation)
        {
            SProcTemplateAttribute sprocAttr = ReflectionHelper.GetAttribute<SProcTemplateAttribute>(pdoType);
            return string.Format(sprocAttr.Template, operation);
        }



        // CX - temporary - refactor
        public static PersistableDataObjectList<T> GetListByQuery<T>(PersistableDataObject parent, string parentForeignKeyName, string sourcePrimaryKeyName,
                                        DataSession session, string sql)
                                        where T : PersistableDataObject, new()
        {
            PersistableDataObjectList<T> list = new PersistableDataObjectList<T>(session, parent, parentForeignKeyName, sourcePrimaryKeyName,
                CommandType.Text, sql, null);

            list.Load();

            if (list.Count > 1)
            {
                // Use the Sort method on any list element to sort the entire list.

                list[0].Sort(list);

                // Sort() depends on the partial method defaultListSort() being implemented in order to provide 
                // any actual sort functionality.
            }
            return list;
        }

        public static PersistableDataObjectList<T> GetListByQuery<T>(PersistableDataObject parent, string parentForeignKeyName, string sourcePrimaryKeyName,
                                        DataSession session, IQueryable query)
                                        where T : PersistableDataObject, new()
        {
            PersistableDataObjectList<T> list = new PersistableDataObjectList<T>(session, parent, parentForeignKeyName, sourcePrimaryKeyName, query);

            list.Load();

            if (list.Count > 1)
            {
                // Use the Sort method on any list element to sort the entire list.

                list[0].Sort(list);

                // Sort() depends on the partial method defaultListSort() being implemented in order to provide 
                // any actual sort functionality.
            }
            return list;
        }


        // CX temp - refactor GetList overrides...
        public static PersistableDataObjectList<T> GetListByQuery<T>(PersistableDataObject parent, string parentForeignKeyName,
        DataSession session, string sql)
        where T : PersistableDataObject, new()
        {
            PersistableDataObjectList<T> list = new PersistableDataObjectList<T>(session, parent, parentForeignKeyName, null, /* foo */
                CommandType.Text, sql, null);

            list.Load();

            if (list.Count > 1)
            {
                // Use the Sort method on any list element to sort the entire list.

                list[0].Sort(list);

                // Sort() depends on the partial method defaultListSort() being implemented in order to provide 
                // any actual sort functionality.
            }
            return list;
        }








        /// <summary>
        /// Return a generic list of PersistableDataObject derived objects.
        /// </summary>
        /// <typeparam name="T">type of object in list</typeparam>
        /// <param name="parent">parent object</param>
        /// <param name="parentForeignKeyName">name of foreign key property on parent object</param>
        /// <param name="session">session object</param>
        /// <param name="sprocName">stored procedure name</param>
        /// <returns></returns>
        public static PersistableDataObjectList<T> GetList<T>(PersistableDataObject parent, string parentForeignKeyName, string foo,
            DataSession session, string sprocName)
                where T : PersistableDataObject, new()
        {
            //return GetList<T>(parent, parentForeignKeyName, session, sprocName, null);
            return GetList<T>(parent, parentForeignKeyName, foo, session, CommandType.StoredProcedure, sprocName);
        }


        public static PersistableDataObjectList<T> GetList<T>(PersistableDataObject parent, string parentForeignKeyName, string sourcePrimaryKeyName,
                                    DataSession session, IQueryable query)
                                    where T : PersistableDataObject, new()
        {
            PersistableDataObjectList<T> list = new PersistableDataObjectList<T>(session, parent, parentForeignKeyName, sourcePrimaryKeyName, query);

            list.Load();

            if (list.Count > 1)
            {
                // Use the Sort method exposed from any list element to sort the entire list.

                list[0].Sort(list);

                // Sort() depends on the partial method defaultListSort() being implemented in order to provide 
                // any actual sort functionality.
            }
            return list;
        }

        public static PersistableDataObjectList<T> GetList<T>(PersistableDataObject parent, string parentForeignKeyName,
                                    DataSession session, IQueryable query)
                                    where T : PersistableDataObject, new()
        {
            PersistableDataObjectList<T> list = new PersistableDataObjectList<T>(session, parent, parentForeignKeyName, null, query);

            list.Load();

            if (list.Count > 1)
            {
                // Use the Sort method exposed from any list element to sort the entire list.

                list[0].Sort(list);

                // Sort() depends on the partial method defaultListSort() being implemented in order to provide 
                // any actual sort functionality.
            }
            return list;
        }



        // CX - temporary - refactor to a Helper class
        public static PersistableDataObjectList<T> GetList<T>(PersistableDataObject parent, string parentForeignKeyName, string sourcePrimaryKeyName,
        DataSession session, CommandType command, string sql)
        where T : PersistableDataObject, new()
        {
            PersistableDataObjectList<T> list = new PersistableDataObjectList<T>(session, parent, parentForeignKeyName, sourcePrimaryKeyName,
                command, sql, null);

            list.Load();

            if (list.Count > 1)
            {
                // Use the Sort method on any list element to sort the entire list.

                list[0].Sort(list);

                // Sort() depends on the partial method defaultListSort() being implemented in order to provide 
                // any actual sort functionality.
            }
            return list;
        }



        /// <summary>
        /// Return a generic list of PersistableDataObject derived objects.
        /// </summary>
        /// <typeparam name="T">type of object in list</typeparam>
        /// <param name="parent">parent object</param>
        /// <param name="parentForeignKeyName">name of foreign key property on parent object</param>
        /// <param name="session">session object</param>
        /// <param name="command">SQL command type</param>
        /// <param name="sql">SQL string</param>
        /// <returns></returns>
        public static PersistableDataObjectList<T> GetList<T>(PersistableDataObject parent, string parentForeignKeyName,
                DataSession session, CommandType command, string sql)
                where T : PersistableDataObject, new()
        {
            PersistableDataObjectList<T> list = new PersistableDataObjectList<T>(session, parent, parentForeignKeyName, null, /* foo */
                CommandType.Text, sql, null);

            list.Load();

            if (list.Count > 1)
            {
                // Use the Sort method on any list element to sort the entire list.

                list[0].Sort(list);

                // Sort() depends on the partial method defaultListSort() being implemented in order to provide 
                // any actual sort functionality.
            }
            return list;
        }


        #region Notification support

        /// <summary>
        /// Gets the data request notification information object for the this item.
        /// </summary>
        /// <returns></returns>
        public static NotificationMessage GetItemUpdateNotificationInfo(PersistableDataObject pdo)
        {
            NotificationMessage message = new NotificationMessage();

            message.Category = pdo.SourceDatabaseTableName;

            message.Item = pdo.DBKey;

            if (pdo.PKeyID != null)
            {
                message.AdditionalInformation.Add(PersistableDataObject.PrimaryKeyPropertyName, pdo.PKeyID.ToString());
            }

            return message;
        }

        public static string GetPrimaryKey(NotificationMessage message)
        {
            // Modified GSS 7/15/09 to handle non-PDO notifications
            if (message.AdditionalInformation.Keys.Contains(PersistableDataObject.PrimaryKeyPropertyName))
            {
                return message.AdditionalInformation[PersistableDataObject.PrimaryKeyPropertyName];
            }
            else
            {
                return string.Empty;
            }
        }

        /// <summary>
        /// Get a key string derived from database table name and provided primary key.
        /// </summary>
        /// <param name="primaryKeyValue">primary key</param>
        /// <returns>key string</returns>
        public static string GetDBKey(PersistableDataObject pdo, object primaryKeyValue)
        {
            return pdo.SourceDatabaseTableName + ":" + primaryKeyValue;
        }

        /// <summary>
        /// Gets the data request notification information object for the current list that
        /// contains this item.
        /// </summary>
        /// <returns></returns>
        public static NotificationMessage GetListUpdateNotificationInfo(PersistableDataObject pdo)
        {
            NotificationMessage notification;

            // Get the base notification for the PDO object
            notification = GetItemUpdateNotificationInfo(pdo);

            // Modify notification if this item is contained in a list
            if (null != pdo.ListContainer)
            {
                notification.Item = Guid.Empty.ToString();
                notification.ListIdentifier = pdo.ListContainer.DBKey;
            }

            return notification;
        }

        #endregion

    }
}
